package shortcircuit;

import shortcircuit.basics.*;
import shortcircuit.basics.powerup.Null;
import shortcircuit.graphics.BeginGameScreen;
import shortcircuit.graphics.GameScreen;
import shortcircuit.graphics.PopUpScreen;
import shortcircuit.graphics.StatsScreen;
import shortcircuit.utilities.Debug;
import shortcircuit.utilities.FileIO;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.*;
import java.awt.geom.Point2D;


/**
 * Brings all of the elements of the ShortCircuit project together.  The
 * environment is assembled and combined with user interface to run on the game's engine.<p>
 * This class is able to add blocks to the game and determine when to clear paths of
 * blocks aligned properly.  Additionally, any user input is recorded and dealed with
 * in this class.
 * 
 * @author Kevin Lewi
 */

public class GameEngine
{
	protected Environment _Environment;
	protected EnvironmentPane environmentPane;
	public final Dimension GRID_DIMENSION = new Dimension(10, 20);
	public ArrayList<ArrayList<Cell>> allCompletePaths;
	public ArrayList<Cell> completePathAddition = new ArrayList<Cell>();
	private ArrayList<ArrayList<Point>> eliminatablePoints;
	private ArrayList<Point> pointsPath; //temp variable used in gettign eliminatablePoints
	private final int BLOCK_SIZE = 40;
	protected double _Time, blockFallRate;
	protected ArrayList<Block> queuedBlocks = new ArrayList<Block>();
	public Display _Display;
	public GamePulse gamePulse;
	public int movesMade;
	protected int _Level = 1, _Score, numberOfMoves, blocksToRemove, blocksRemoved;
	public GameScreen gameScreen;
	public StatsScreen statsScreen;
	private boolean readyToClear, readyToFade, readyToBleach, isInitiallyFaded;
	public PopUpScreen popUpScreen;
	protected FileIO fileSaver;
	public ArrayList<PowerUp> powerUps;
	private int powerUpAddingCounter;
	protected int fallingBlocksSize;
	private int endGameWaiter;
	boolean interrupted = false;

	//mouse input variables


	/**
	 * Creates a new Display to start the game.
	 * 
	 * @author Kevin Lewi
	 */
	/*
	public GameEngine()
	{
		readyToClear = true;
		_Display = new Display();
		gamePulse = new GamePulse();
	}
	 */

	public GameEngine(Display theDisplay, int typeOfGame)
	{
		powerUps = new ArrayList<PowerUp>();
		for(int i = 0; i < 3; i++)
		{
			powerUps.add(new Null());
		}
		fileSaver = new FileIO("records.txt");
		readyToClear = false;
		readyToFade = false;
		isInitiallyFaded = false;
		_Display = theDisplay;
		if(_Display == null)
			_Display = new Display(this);
		if(typeOfGame == PopUpScreen.ENDLESS)
			popUpScreen = new BeginGameScreen(typeOfGame, this);
		gamePulse = new GamePulse();
	}

	public int getBlocksRemaining()
	{
		return blocksToRemove - blocksRemoved >= 0 ? blocksToRemove - blocksRemoved : 0;
	}
	
	public int getMovesLeft()
	{
		return numberOfMoves - movesMade;
	}
	
	public void stopAllThreads()
	{
		gamePulse = null;
	}

	/**
	 * Creates a new instance of Environment with a blank map.
	 * 
	 * @author Kevin Lewi
	 */
	public void createMap(EnvironmentPane theEnvPane)
	{
		environmentPane = theEnvPane;
		if(theEnvPane.getEnvironment() == null)
			_Environment = new Environment(GRID_DIMENSION);
		else
			_Environment = theEnvPane.getEnvironment();
		setCellsHoverable();
		try
		{
			gamePulse.start();
		}
		catch(IllegalThreadStateException e) {}
		_Display.repaint();
	}

	/**
	 * Refreshes elements of the game that are controlled by the timer.  
	 * This includes the checking of a possible algorithm path, recalculation
	 * of each of the new positions and velocities of each block, random generation
	 * of each new block, and animation of walls on fire and clearing blocks.
	 * 
	 * @author Kevin Lewi
	 */
	public void update()
	{
		try
		{
			checkForCompletePath();
			addToCompletePath();
		}
		catch(NullPointerException e) {}
		if(allCompletePaths != null && allCompletePaths.size() > 0)
		{
			readyToBleach = true;
			clearAnimations();
		}
		if(allCompletePaths.size() == 0)
			fallingBlocksSize = environmentPane.step();
		//statsScreen.update();
		setCellsHoverable();
		engineUpdate();
		_Display.repaint();
		
		if(allCompletePaths.size() == 0
				&& environmentPane.getFallingBlocksSize() == 0
				&& fallingBlocksSize == 0)
		{
			endGameWaiter++;
			if(endGameWaiter > 50)
			{
				endGameWaiter = 0;
				checkEndGame();
			}
		}

	}

	protected void addToCompletePath()
	{
		if(completePathAddition == null)
			return;
		ArrayList<Cell> tempPath = new ArrayList<Cell>();
		for(int i = 0; i < completePathAddition.size(); i++)
		{
			if(completePathAddition.get(i).hasBlock())
				tempPath.add(completePathAddition.get(i));
		}
		if(tempPath.size() > 0)
			allCompletePaths.add(tempPath);
	}
	
	private void clearAnimations()
	{
		if(readyToBleach){
			readyToFade = false;
			readyToBleach = false;
		}
		for(int x = 0; x < allCompletePaths.size(); x++)
		{
			for(int y = 0; y < allCompletePaths.get(x).size(); y++)
			{
				readyToFade = allCompletePaths.get(x).get(y).getBlock().bleach();
			}
		}

		if(readyToFade && !isInitiallyFaded)
		{
			for(int x = 0; x < allCompletePaths.size(); x++)
			{
				for(int y = 0; y < allCompletePaths.get(x).size(); y++)
				{
					allCompletePaths.get(x).get(y).getBlock().setTransparency(1.0f + y * 0.2f);
				}
			}
			readyToClear = true;
			isInitiallyFaded = true;
		}
		if(readyToFade)
		{
			for(int x = 0; x < allCompletePaths.size(); x++)
			{
				for(int y = 0; y < allCompletePaths.get(x).size(); y++)
				{
					Block theBlock = allCompletePaths.get(x).get(y).getBlock();
					readyToClear = theBlock.fade();
					if(theBlock.hasPowerUp() && theBlock.getTransparency() < 0.75f)
					{
						powerUps.add(0, theBlock.getPowerUp());
						powerUps.remove(powerUps.size() - 1);
						theBlock.removePowerUp();
					}
				}
			}
		}
			
		if(readyToClear)
		{
			clearCells(allCompletePaths);
			readyToClear = false;
			isInitiallyFaded = false;
		}
	}

	private ArrayList<Rectangle> getClearRectangles(int rectangleSize)
	{
		ArrayList<Rectangle> clearRectangles = new ArrayList<Rectangle>();
		for(int x = 0; x < allCompletePaths.size(); x++)
		{
			for(int y = 0; y < allCompletePaths.get(x).size(); y++)
			{
				for(int indexX = 0; indexX < 40 / rectangleSize; indexX++)
				{
					for(int indexY = 0; indexY < 40 / rectangleSize; indexY++)
					{
						Rectangle tempRect = new Rectangle(_Environment.getLocation(allCompletePaths.get(x).get(y)).x * 40 + rectangleSize * indexX, 
								-_Environment.getLocation(allCompletePaths.get(x).get(y)).y * 40 + rectangleSize * indexY, rectangleSize, rectangleSize);
						tempRect.setLocation(tempRect.getLocation().x, tempRect.getLocation().y + 60);
						clearRectangles.add(tempRect);
					}
				}
			}
		}
		return clearRectangles;
	}

	/*	public void removeBlocks()
	{
		ArrayList<Cell> traversedCells = new ArrayList<Cell>();
		for(int x = 0; x < allCompletePaths.size(); x++)
		{
			for(int y = 0; y < allCompletePaths.get(x).size(); y++)
			{
				boolean alreadyExists = false;
				for(int i = 0; i < traversedCells.size(); i++)
				{
					if(allCompletePaths.get(x).get(y) == traversedCells.get(i))
					{
						alreadyExists = true;
						break;
					}
				}
				if(!alreadyExists)
				{
					traversedCells.add(allCompletePaths.get(x).get(y));
					blocksRemoved++;
					_Score += 10;
					if(_Level*100<_Score)
						_Level++;
				}
			}
		}
	}
	 */
	private void generatePointPath()
	{
		eliminatablePoints = new ArrayList<ArrayList<Point>>();
		for(int x = 0; x < allCompletePaths.size(); x++)
		{
			pointsPath = new ArrayList<Point>();
			for(int y = 0; y < allCompletePaths.get(x).size(); y++)
			{
				boolean isCurved = false;
				Cell currentCell = allCompletePaths.get(x).get(y);
				if(currentCell.getBlock().getClass().getName().contains("4") ||
						currentCell.getBlock().getClass().getName().contains("5"))
					isCurved = true;
				Point prevLoc = y > 0 ? 
						_Environment.getLocation(allCompletePaths.get(x).get(y - 1)) : 
							new Point(-1, _Environment.getLocation(currentCell).y);
						Point currentLoc = _Environment.getLocation(currentCell);
						Point nextLoc = y < allCompletePaths.get(x).size() - 1 ? 
								_Environment.getLocation(allCompletePaths.get(x).get(y + 1)) : 
									new Point(_Environment.getGrid().length, -2);
								if(currentLoc.x - 1 == prevLoc.x && currentLoc.y + 1 == nextLoc.y)
									generatePoints(0, isCurved, currentLoc);
								if(currentLoc.x - 1 == prevLoc.x && currentLoc.x + 1 == nextLoc.x)
									generatePoints(1, isCurved, currentLoc);
								if(currentLoc.x - 1 == prevLoc.x && currentLoc.y - 1 == nextLoc.y)
									generatePoints(2, isCurved, currentLoc);
								if(currentLoc.y + 1 == prevLoc.y && currentLoc.x - 1 == nextLoc.x)
									generatePoints(3, isCurved, currentLoc);
								if(currentLoc.y + 1 == prevLoc.y && currentLoc.x + 1 == nextLoc.x)
									generatePoints(4, isCurved, currentLoc);
								if(currentLoc.y + 1 == prevLoc.y && currentLoc.y - 1 == nextLoc.y)
									generatePoints(5, isCurved, currentLoc);
								if(currentLoc.x + 1 == prevLoc.x && currentLoc.x - 1 == nextLoc.x)
									generatePoints(6, isCurved, currentLoc);
								if(currentLoc.x + 1 == prevLoc.x && currentLoc.y + 1 == nextLoc.y)
									generatePoints(7, isCurved, currentLoc);
								if(currentLoc.x + 1 == prevLoc.x && currentLoc.y - 1 == nextLoc.y)
									generatePoints(8, isCurved, currentLoc);
								if(currentLoc.y - 1 == prevLoc.y && currentLoc.x - 1 == nextLoc.x)
									generatePoints(9, isCurved, currentLoc);
								if(currentLoc.y - 1 == prevLoc.y && currentLoc.y + 1 == nextLoc.y)
									generatePoints(10, isCurved, currentLoc);
								if(currentLoc.y - 1 == prevLoc.y && currentLoc.x + 1 == nextLoc.x)
									generatePoints(11, isCurved, currentLoc);
			}
			eliminatablePoints.add(pointsPath);
		}
	}

	private void generatePoints(int theSituation, boolean isCurved, Point currentLoc)
	{
		Point tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
		switch(theSituation)
		{
		case 0:
			if(isCurved)
			{
				for(double i = -Math.PI / 2 + 0.2; i < 0 - 0.2; i += Math.PI / 100)
				{
					tempPoint.x += -5 + (int) (25 * Math.cos(i));
					tempPoint.y += BLOCK_SIZE + 5 + (int) (25 * Math.sin(i));
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			if(!isCurved)
			{
				for(int i = 0; i < 20; i++)
				{
					tempPoint.x += i;
					tempPoint.y += 20;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}					
				for(int i = 20; i < 40; i++)
				{
					tempPoint.x += 20;
					tempPoint.y += i;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			break;
		case 1:
			for(int i = 0; i < 40; i++)
			{
				tempPoint.x += i;
				tempPoint.y += 21;
				pointsPath.add(tempPoint);
				tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
			}
			break;
		case 2:
			if(isCurved)
			{
				for(double i = Math.PI / 2 - 0.2; i > 0 + 0.2; i -= Math.PI / 100)
				{
					tempPoint.x += -5 + (int) (25 * Math.cos(i));
					tempPoint.y += -3 + (int) (25 * Math.sin(i));
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			if(!isCurved)
			{
				for(int i = 0; i < 20; i++)
				{
					tempPoint.x += i;
					tempPoint.y += 20;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}					
				for(int i = 20; i > 0; i--)
				{
					tempPoint.x += 20;
					tempPoint.y += i;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			break;
		case 3:
			if(isCurved)
			{
				for(double i = 0 + 0.2; i > -Math.PI / 2 - 0.2; i -= Math.PI / 100)
				{
					tempPoint.x += -5 + (int) (25 * Math.cos(i));
					tempPoint.y += BLOCK_SIZE + 5 + (int) (25 * Math.sin(i));
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			if(!isCurved)
			{
				for(int i = 40; i > 20; i--)
				{
					tempPoint.x += 20;
					tempPoint.y += i;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}					
				for(int i = 20; i > 0; i--)
				{
					tempPoint.x += i;
					tempPoint.y += 20;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			break;
		case 4:
			if(isCurved)
			{
				for(double i = Math.PI + 0.2; i < 3 * Math.PI / 2 - 0.2; i += Math.PI / 100)
				{
					tempPoint.x += BLOCK_SIZE + 5 + (int) (25 * Math.cos(i));
					tempPoint.y += BLOCK_SIZE + 5 + (int) (25 * Math.sin(i));
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			if(!isCurved)
			{
				for(int i = 40; i > 20; i--)
				{
					tempPoint.x += 20;
					tempPoint.y += i;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}					
				for(int i = 20; i < 40; i++)
				{
					tempPoint.x += i;
					tempPoint.y += 20;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			break;
		case 5:
			for(int i = 40; i > 0; i--)
			{
				tempPoint.x += 21;
				tempPoint.y += i;
				pointsPath.add(tempPoint);
				tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
			}
			break;
		case 6:
			for(int i = 40; i > 0; i--)
			{
				tempPoint.x += i;
				tempPoint.y += 21;
				pointsPath.add(tempPoint);
				tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
			}
			break;
		case 7:
			if(isCurved)
			{
				for(double i = 3 * Math.PI / 2 - 0.2; i > Math.PI + 0.2; i -= Math.PI / 100)
				{
					tempPoint.x += BLOCK_SIZE + 5 + (int) (25 * Math.cos(i));
					tempPoint.y += BLOCK_SIZE + 5 + (int) (25 * Math.sin(i));
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			if(!isCurved)
			{
				for(int i = 40; i > 20; i--)
				{
					tempPoint.x += i;
					tempPoint.y += 20;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}					
				for(int i = 20; i < 40; i++)
				{
					tempPoint.x += 20;
					tempPoint.y += i;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			break;
		case 8:
			if(isCurved)
			{
				for(double i = Math.PI / 2 + 0.2; i < Math.PI - 0.2; i += Math.PI / 100)
				{
					tempPoint.x += BLOCK_SIZE + 5 + (int) (25 * Math.cos(i));
					tempPoint.y += -5 + (int) (25 * Math.sin(i));
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			if(!isCurved)
			{
				for(int i = 40; i > 20; i--)
				{
					tempPoint.x += i;
					tempPoint.y += 20;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}				
				for(int i = 20; i > 0; i--)
				{
					tempPoint.x += 20;
					tempPoint.y += i;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			break;
		case 9:
			if(isCurved)
			{
				for(double i = 0 + 0.2; i < Math.PI / 2 - 0.2; i += Math.PI / 100)
				{
					tempPoint.x += -5 + (int) (25 * Math.cos(i));
					tempPoint.y += -5 + (int) (25 * Math.sin(i));
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			if(!isCurved)
			{
				for(int i = 0; i < 20; i++)
				{
					tempPoint.x += 20;
					tempPoint.y += i;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}				
				for(int i = 20; i > 0; i--)
				{
					tempPoint.x += i;
					tempPoint.y += 20;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			break;
		case 10:
			for(int i = 0; i < 40; i++)
			{
				tempPoint.x += 21;
				tempPoint.y += i;
				pointsPath.add(tempPoint);
				tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
			}
			break;
		case 11:
			if(isCurved)
			{
				for(double i = Math.PI - 0.2; i > Math.PI / 2 + 0.2; i -= Math.PI / 100)
				{
					tempPoint.x += BLOCK_SIZE + 5 + (int) (25 * Math.cos(i));
					tempPoint.y += -3 + (int) (25 * Math.sin(i));
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			if(!isCurved)
			{
				for(int i = 0; i < 20; i++)
				{
					tempPoint.x += 20;
					tempPoint.y += i;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}				
				for(int i = 20; i < 40; i++)
				{
					tempPoint.x += i;
					tempPoint.y += 20;
					pointsPath.add(tempPoint);
					tempPoint = new Point(currentLoc.x * BLOCK_SIZE, currentLoc.y * BLOCK_SIZE);
				}
			}
			break;
		}

	}

	public void engineUpdate()
	{

	}

	public void checkEndGame()
	{

	}

	/**
	 * Uses a HashSet to keep track of all of the cells in queue for deletion.  Determines
	 * which walls of each block are to be set on fire by checking for neighboring blcoks
	 * that also exist in the queue.
	 * 
	 * @author Kevin Lewi
	 */
	private void setWallsOnFire()
	{
		HashSet<Cell> hashedCells = new HashSet<Cell>();
		HashMap<Point, int[]> affectedWalls = new HashMap<Point, int[]>();

		for(int x = 0; x < allCompletePaths.size(); x++)
		{
			for(int y = 0; y < allCompletePaths.get(x).size(); y++)
			{
				hashedCells.add(allCompletePaths.get(x).get(y));
			}
		}



		Iterator<Cell> itr = hashedCells.iterator();
		while(itr.hasNext())
		{

			Cell c = itr.next();
			affectedWalls.put(_Environment.getLocation(c), getAffectedWalls(c, hashedCells));
		}

		Iterator<Point> itr2 = affectedWalls.keySet().iterator();
		while(itr2.hasNext())
		{
			Point p = itr2.next();
			_Environment.getCell(p.x, p.y).getBlock().setWallsOnFire(affectedWalls.get(p));
		}

	}

	/**
	 * Used by setWallsOnFire to determine whether or not adjacent cells exist
	 * in the HashSet of hashed cells.  If not, the corresponding wall is added.
	 * 
	 * @param theCell
	 * @param hashedCells
	 * @return The int array of affectedWalls
	 * @author Kevin Lewi
	 */
	private int[] getAffectedWalls(Cell theCell, HashSet<Cell> hashedCells)
	{
		ArrayList<Integer> theWalls = new ArrayList<Integer>();
		Point cellLocation = _Environment.getLocation(theCell);
		if(!hashedCells.contains(_Environment.getCell(cellLocation.x - 1, cellLocation.y)))
			theWalls.add(Wall.WEST);
		if(!hashedCells.contains(_Environment.getCell(cellLocation.x, cellLocation.y + 1)))
			theWalls.add(Wall.NORTH);
		if(!hashedCells.contains(_Environment.getCell(cellLocation.x + 1, cellLocation.y)))
			theWalls.add(Wall.EAST);
		if(!hashedCells.contains(_Environment.getCell(cellLocation.x, cellLocation.y - 1)))
			theWalls.add(Wall.SOUTH);
		int[] arrayWalls = new int[theWalls.size()];
		for(int x = 0; x < theWalls.size(); x++)
			arrayWalls[x] = theWalls.get(x);
		return arrayWalls;
	}

	/**
	 * Searches each possible path that begins from the leftmost column and
	 * detect when the path has reached the rightmost column using the pathTraversal
	 * method.  After each complete path is found, all walls are cleared of their
	 * traversal status before the next block on the leftmost column is checked.
	 * 
	 * @author Kevin Lewi
	 */
	public void checkForCompletePath()
	{
		allCompletePaths = new ArrayList<ArrayList<Cell>>();
		for(int index = 0; index < _Environment.getGrid()[0].length; index++)
		{
			if(_Environment.getCell(0, index).hasBlock())
			{
				pathTraversal(new ArrayList<Cell>(), _Environment.getCell(0, index), Wall.WEST);
				clearAllWalls();
			}
		}
		orderCompletePath();
	}
	
	public void orderCompletePath()
	{
		ArrayList<ArrayList<Cell>> tempCompletePaths = new ArrayList<ArrayList<Cell>>();
		for(int i = 0; i < allCompletePaths.size(); i++)
		{
			ArrayList<Cell> tempPath = new ArrayList<Cell>();
			for(int x = 0; x < _Environment.getGrid().length; x++)
			{
				for(int y = _Environment.getGrid()[0].length - 1; y >= 0 ; y--)
				{
					for(int index = 0; index < allCompletePaths.get(i).size(); index++)
					{
						if(_Environment.getCell(x, y) == allCompletePaths.get(i).get(index))
						{
							tempPath.add(allCompletePaths.get(i).get(index));
							break;
						}
					}
				}
			}
			tempCompletePaths.add(tempPath);
		}
		allCompletePaths = tempCompletePaths;
	}

	/**
	 * Recursively checks the cell to see where to go next.  If no paths are available, then
	 * the current cell is removed and the recursion is ended.  Cells that have already been
	 * traversed in a certain direction cannot be traversed in that same direction again.
	 * 
	 * @param completePath The ongoing path that is being added to each time the recursion continues
	 * @param startCell The current cell being examined
	 * @param lastDirection The wall direction in which the cell is being entered from
	 * @return Whether or not the cell should really be added to the path
	 * @author Kevin Lewi
	 */
	private boolean pathTraversal(ArrayList<Cell> completePath, Cell startCell, int lastDirection)
	{
		boolean isAdded = false, isReallyAdded = false;
		
		boolean[] wallStatus = new boolean[5];
		for(int index = 0; index < wallStatus.length; index++)
		{
			//stores the status of each of the results from checkNeighborCells
			wallStatus[index] = checkNeighborCells(startCell, index, lastDirection);
		}
		if(wallStatus[1])
		{
			completePath.add(startCell);
			startCell.getBlock().getWall(Wall.WEST).setTraversed(true);
			isAdded = pathTraversal(completePath, _Environment.getCell(_Environment.getLocation(startCell).x - 1, _Environment.getLocation(startCell).y), Wall.EAST);
			if(isAdded)
				isReallyAdded = true;
		}
		if(wallStatus[2])
		{
			completePath.add(startCell);
			startCell.getBlock().getWall(Wall.NORTH).setTraversed(true);
			isAdded = pathTraversal(completePath, _Environment.getCell(_Environment.getLocation(startCell).x, _Environment.getLocation(startCell).y + 1), Wall.SOUTH);
			if(isAdded)
				isReallyAdded = true;
		}
		if(wallStatus[3])
		{
			completePath.add(startCell);
			startCell.getBlock().getWall(Wall.EAST).setTraversed(true);
			isAdded = pathTraversal(completePath, _Environment.getCell(_Environment.getLocation(startCell).x + 1, _Environment.getLocation(startCell).y), Wall.WEST);
			if(isAdded)
				isReallyAdded = true;
		}
		if(wallStatus[4])
		{
			completePath.add(startCell);
			startCell.getBlock().getWall(Wall.SOUTH).setTraversed(true);
			isAdded = pathTraversal(completePath, _Environment.getCell(_Environment.getLocation(startCell).x, _Environment.getLocation(startCell).y - 1), Wall.NORTH);
			if(isAdded)
				isReallyAdded = true;
		}
		if(wallStatus[0])
		{
			isReallyAdded = true;
		}
		if(!wallStatus[0] && 
				!wallStatus[1] && 
				!wallStatus[2] && 
				!wallStatus[3] && 
				!wallStatus[4])
		{
			isReallyAdded = false;
		}
		if(isReallyAdded)
			completePath.add(startCell);
		else
			completePath.remove(startCell);
		if(wallStatus[0])
			allCompletePaths.add(completePath);
		return isReallyAdded;
	}

	/**
	 * Simplifies the pathTraversal method by checking a specified
	 * situation and returning a boolean value.<p>
	 * 0 - The cell connects to the right wall<br>
	 * 1 - The west neighbor is free and has not been traversed in this direction before<br>
	 * 2 - The north neighbor is free and has not been traversed in this direction before<br>
	 * 3 - The east neighbor is free and has not been traversed in this direction before<br>
	 * 4 - The south neighbor is free and has not been traversed in this direction before<br>
	 * -1 - None of the above conditions have been met
	 * 
	 * @param currentCell The cell being checked
	 * @param theSituation The situation to verify
	 * @param lastDirection The last wall direction in which the cell has been entered from
	 * @return The results of the verification of the situation on the cell
	 * @author Kevin Lewi
	 */
	private boolean checkNeighborCells(Cell currentCell, int theSituation, int lastDirection)
	{
		if(theSituation == 0 &&
				currentCell.hasBlock() &&
				currentCell.getBlock().getWall(Wall.EAST).isFree(lastDirection) &&
				_Environment.getLocation(currentCell).x == _Environment.getGrid().length - 1)
			return true;
		if(theSituation == 1 &&
				currentCell.getBlock().getWall(Wall.WEST).isFree(lastDirection) &&
				!currentCell.getBlock().getWall(Wall.WEST).isTraversed() && 
				_Environment.getLocation(currentCell).x - 1 >= 0 &&
				_Environment.getCell(_Environment.getLocation(currentCell).x - 1, _Environment.getLocation(currentCell).y).hasBlock() &&
				_Environment.getCell(_Environment.getLocation(currentCell).x - 1, _Environment.getLocation(currentCell).y).getBlock().getWall(Wall.EAST).isFree())
			return true;
		if(theSituation == 2 &&
				currentCell.getBlock().getWall(Wall.NORTH).isFree(lastDirection) &&
				!currentCell.getBlock().getWall(Wall.NORTH).isTraversed() && 
				_Environment.getLocation(currentCell).y + 1 < _Environment.getGrid()[0].length &&
				_Environment.getCell(_Environment.getLocation(currentCell).x, _Environment.getLocation(currentCell).y + 1).hasBlock() &&
				_Environment.getCell(_Environment.getLocation(currentCell).x, _Environment.getLocation(currentCell).y + 1).getBlock().getWall(Wall.SOUTH).isFree())
			return true;
		if(theSituation == 3 &&
				currentCell.getBlock().getWall(Wall.EAST).isFree(lastDirection) &&
				!currentCell.getBlock().getWall(Wall.EAST).isTraversed() && 
				_Environment.getLocation(currentCell).x + 1 < _Environment.getGrid().length &&
				_Environment.getCell(_Environment.getLocation(currentCell).x + 1, _Environment.getLocation(currentCell).y).hasBlock() &&
				_Environment.getCell(_Environment.getLocation(currentCell).x + 1, _Environment.getLocation(currentCell).y).getBlock().getWall(Wall.WEST).isFree())
			return true;
		if(theSituation == 4 &&
				currentCell.getBlock().getWall(Wall.SOUTH).isFree(lastDirection) &&
				!currentCell.getBlock().getWall(Wall.SOUTH).isTraversed() && 
				_Environment.getLocation(currentCell).y - 1 >= 0 &&
				_Environment.getCell(_Environment.getLocation(currentCell).x, _Environment.getLocation(currentCell).y - 1).hasBlock() &&
				_Environment.getCell(_Environment.getLocation(currentCell).x, _Environment.getLocation(currentCell).y - 1).getBlock().getWall(Wall.NORTH).isFree())
			return true;
		return false;
	}

	/**
	 * Sets traversal status of all walls from cells with blocks to false.
	 * 
	 * @author Kevin Lewi
	 */
	private void clearAllWalls()
	{
		for(int x = 0; x < _Environment.getGrid().length; x++)
			for(int y = 0; y < _Environment.getGrid()[0].length; y++)
				for(int z = 0; z < 4; z++)
					if(_Environment.getCell(x, y).hasBlock())
						_Environment.getCell(x, y).getBlock().getWall(z).setTraversed(false);
	}

	/**
	 * Defines the cells that are able to be hovered by the mouse.
	 * This consists of any cell that exists on the top layer of the
	 * blocks in each Environment.
	 * 
	 * @author Kevin Lewi
	 */
	public void setCellsHoverable()
	{
		for(int x = 0; x < _Environment.getGrid().length; x++)
		{
			for(int y = 0; y < _Environment.getGrid()[0].length; y++)
			{
				_Environment.getCell(x, y).setHoverable(false);
				if(allCompletePaths != null && allCompletePaths.size() == 0 && !(getMovesLeft() == 0 && this.getClass().getName().contains("Puzzle")) && (_Environment.getCell(x, y).hasBlock() || y == 0 || _Environment.getCell(x, y - 1).hasBlock()))
					_Environment.getCell(x, y).setHoverable(true);
				if(allCompletePaths != null)
				{
					for(int i = 0; i < allCompletePaths.size(); i++)
					{
						for(int j = 0; j < allCompletePaths.get(i).size(); j++)
						{
							if(allCompletePaths.get(i).get(j) == _Environment.getCell(x, y))
								_Environment.getCell(x, y).setHoverable(false);
						}
					}
				}
			}
		}	
		
		/*
		boolean currentBlockStatus;
		boolean lastBlockStatus;
		boolean cellAdded;
		Cell currentCell;
		Cell lastCell;

		for(int x = 0; x < _Environment.getGrid().length; x++)
			for(int y = 0; y < _Environment.getGrid()[0].length; y++)
				_Environment.getCell(x, y).setHoverable(false);

		for(int x = 0; x < _Environment.getGrid().length; x++)
		{
			cellAdded = false;
			currentBlockStatus = false;
			lastBlockStatus = currentBlockStatus;
			currentCell = null;
			lastCell = currentCell;
			for(int y = 0; y < _Environment.getGrid()[0].length; y++)
			{
				if(cellAdded)
					break;
				currentCell = _Environment.getCell(x, y);
				currentBlockStatus = _Environment.getCell(x, y).hasBlock();
				if(y == _Environment.getGrid()[0].length - 1 && currentBlockStatus)
				{
					currentCell.setHoverable(true);
				}
				else if(lastBlockStatus &&
						!currentBlockStatus)
				{
					cellAdded = true;
					lastCell.setHoverable(true);
				}
				lastBlockStatus = currentBlockStatus;
				lastCell = currentCell;
			}
		}
		 */
	}
	
	/**
	 * Calls the Environment to clear the cells specified, removing blocks in the cells if 
	 * possible.
	 * 
	 * @param theCells The array of the cells to be cleared
	 * @author David Li
	 */
	public void clearCells(ArrayList<ArrayList<Cell>> theCells){
		int[] bottomDeletedBlocks = new int[_Environment.getGrid().length];
		Cell currentCell;
		int deleteCounter = 0;
		
		for(int x=0;x<theCells.size();x++)
			for(int y=0;y<theCells.get(x).size();y++){
				currentCell = theCells.get(x).get(y);
				if(bottomDeletedBlocks[_Environment.getLocation(currentCell).x]-1>_Environment.getLocation(currentCell).y)
					bottomDeletedBlocks[_Environment.getLocation(currentCell).x]=_Environment.getLocation(currentCell).y+1;
				if(_Environment.clearCell(currentCell)){
					blocksRemoved++;
					deleteCounter++;
					_Score += 10;
					if(deleteCounter - 7 > 0)
						_Score += 5 * Math.pow(deleteCounter, 1.3);
					if(!this.getClass().getName().contains("Puzzle"))
							_Level = (int) (Math.log(_Score / 100) / Math.log(2) + 1);
				}
				environmentPane.changeTopBlocks();
			}
		if(statsScreen != null)
			statsScreen.update();
		setCellsHoverable();

		for(int x=0;x<_Environment.getGrid().length;x++)
			for(int y=bottomDeletedBlocks[x];y<_Environment.getGrid()[x].length;y++){
				currentCell = _Environment.getCell(x,y);
				if(currentCell.hasBlock()){
					Block currentBlock = currentCell.getBlock();
					_Environment.clearCell(currentCell);
					environmentPane.changeTopBlocks();
					environmentPane.addBlock(currentBlock,new Point(x,y+1));
				}
			}
		completePathAddition = null;
	}

	/**
	 * Generates a random Block located on the top of a random column in the Environment.
	 * 
	 * @param restrictedBlocks The blocks to be excluded from the pool
	 * @return The random block to be inserted in the environment.
	 * @author David Li
	 */
	public void generateRandomBlock(int[] restrictedBlocks){
		Block newBlock = createRandomBlock(restrictedBlocks);
		newBlock.generatePowerUp(_Level);
		queuedBlocks.add(newBlock);
		environmentPane.addRandomBlock(queuedBlocks.remove(0), new Random().nextInt(_Environment.getGrid().length));
		_Score += 3;
		if(statsScreen != null)
			statsScreen.update();
	}
	
	public Block createRandomBlock(int[] restrictedBlocks){
		Block newBlock = null;
		int blockType;
		boolean isRestricted = false;
		Random randGen = new Random();
		do
		{
			isRestricted = false;
			blockType = randGen.nextInt(6)+1;
			for(int i = 0; i < restrictedBlocks.length; i++)
			{
				if(restrictedBlocks[i] == blockType)
				{
					isRestricted = true;
					break;
				}
			}
		}while(isRestricted);

		String className = "shortcircuit.basics.block.Block" + blockType;
		try{
			newBlock = (Block) Class.forName(className).newInstance();
		}
		catch(ClassNotFoundException e) { System.out.println("Error: Class Not Found Exception"); }
		catch(InstantiationException e) { System.out.println("Error: Instantiation Exception"); }
		catch(IllegalAccessException e) { System.out.println("Error: Illegal Access Exception"); }
		return newBlock;
	}

	public EnvironmentPane getEnvironmentPane(){
		return environmentPane;
	}

	public int getScore(){
		return _Score;
	}
	
	public int getHighScore()
	{
		int highScore = fileSaver.getHighScore();
		if(getScore() > highScore)
		{
			fileSaver.setHighScore(getScore());
			return getScore();
		}
		return highScore;
	}

	public int getLevel(){
		return _Level;
	}

	public ArrayList<Block> getQueuedBlocks(){
		return queuedBlocks;
	}
	
	public int getBlocksToRemove(){
		return blocksToRemove;
	}
	
	/*	/**
	 * Controls the mouse clicking and moving and sets which block is
	 * selected by the user.
	 * 
	 * @author Kevin Lewi
	public class MouseInput extends MouseAdapter implements MouseMotionListener
	{
		GameScreen gameScreen;
		Cell selectedCell;
		protected Element currentSelectedElement, previousSelectedElement,
		currentElement, previousElement;
		boolean isBlockTransitioning;

		/**
	 * Initializes all class-scope variables to be used in this listener.
	 * 
	 * @author Kevin Lewi
		public MouseInput(GameScreen theScreen, String s)
		{
			gameScreen = theScreen;
			selectedCell = new Cell();
			previousSelectedElement = null;
			currentSelectedElement = null;
			previousElement = null;
			currentElement = null;
			isBlockTransitioning = false;
		}

		public MouseInput() {}

		/**
	 * Converts a set of cursor coordinates into an element if the coordinate matches
	 * with an element.
	 * 
	 * @param cursorCoordinates The coordinates of the cursor
	 * @return The corresponding element of the cursor coordinates
	 * @author Kevin Lewi
		public Element convertToElement(Point cursorCoordinates)
		{
			Point cellCoord = new Point((int) Math.floor(((double) cursorCoordinates.x - (double) GameScreen.ENVIRONMENT_X_OFFSET) / 40.0), (int) Math.floor(((double) cursorCoordinates.y - (double) GameScreen.ENVIRONMENT_Y_OFFSET) / 40.0));
			return _Environment.getCell(cellCoord.x, cellCoord.y);
		}

		/**
	 * Determines which cell the user is hovering his mouse over and initiates the
	 * hover animation to the appropriate cell.  Ends by calling Display's paint method.
	 * 
	 * @author Kevin Lewi
		public void mouseMoved(MouseEvent e)
		{
			previousElement = currentElement;
			Point cursorCoordinates = new Point(e.getX(), Display.getFrames()[0].getHeight() - e.getY() - Display.TOOLBAR_OFFSET);

			currentElement = convertToElement(cursorCoordinates);
			if(currentElement != null && ((Cell) currentElement).isHoverable())
			{
				((Cell) currentElement).setHovered(true);
			}
			if((Cell) previousElement != (Cell) currentElement && previousElement != null)
				((Cell) previousElement).setHovered(false);
			//if(currentSelectedElement != null)
			//	_Display.passTransparentObjects(selectedCell, _Environment.getLocation((Cell) currentElement).x);
			gameScreen.repaint();
		}

		public void mouseDragged(MouseEvent e) {}

		/**
	 * Deals with the selecting of elements based on mouse clicking and the appropriate
	 * actions if the element is a selectable cell.
	 * 
	 * @param e The MouseEvent passed into this method
	 * @author Kevin Lewi
		public void mousePressed(MouseEvent e)
		{
			Element tempElement = currentSelectedElement;
			previousSelectedElement = currentSelectedElement;
			if(currentElement != null)
				currentSelectedElement = true ? 
						currentElement : currentSelectedElement;
			Cell prevCell;
			try
			{
				prevCell = previousSelectedElement.getClass().getName().equals(Cell.class.getName()) ? 
						(Cell) previousSelectedElement : null;
			}
			catch(NullPointerException f) { prevCell = null; }
			Cell currentCell = currentSelectedElement.getClass().getName().equals(Cell.class.getName()) ? 
					(Cell) currentSelectedElement : null;

					if(e.getButton() == MouseEvent.BUTTON3 && isBlockTransitioning)
						deselectCell(currentCell, prevCell);
					if(e.getButton() == MouseEvent.BUTTON1 && currentCell.isHovered() && !isBlockTransitioning)
						selectCell(currentCell);
					else if(!currentCell.hasBlock() && _Environment.getCell(_Environment.getLocation(currentCell).x, _Environment.getLocation(currentCell).y).isHoverable() &&
							e.getButton() == MouseEvent.BUTTON1 && prevCell != null && isBlockTransitioning){
						moveBlock(currentCell, prevCell);
						for(int y=_Environment.getLocation(prevCell).y;y<_Environment.getGrid()[0].length;y++){
							if(_Environment.getGrid()[_Environment.getLocation(prevCell).x][y].hasBlock())
							{
								environmentPane.addBlock(_Environment.getCell(_Environment.getLocation(prevCell).x,y).getBlock(),new Point(_Environment.getLocation(prevCell).x,y+1));
								_Environment.clearCell(_Environment.getCell(_Environment.getLocation(prevCell).x,y));
							}
						}
					}
					else if(currentCell.hasBlock() &&
							e.getButton() == MouseEvent.BUTTON1 && prevCell != null && isBlockTransitioning){
						moveBlock(currentCell, prevCell);
					}

					else
					{
						currentSelectedElement = tempElement;
					}
					gameScreen.repaint();
		}

		void deselectCell(Cell currentCell, Cell prevCell)
		{
			isBlockTransitioning = false;
			if(prevCell != null)
			{
				if(prevCell == currentCell)
				{
					currentCell.setSelected(false);
					currentCell.setBlock(selectedCell.getBlock());
				}
				else
				{
					prevCell.setSelected(false);
					prevCell.setBlock(selectedCell.getBlock());
				}
			}
			//_Display.passTransparentObjects(null, _Environment.getLocation((Cell) currentElement).x);
			currentSelectedElement = null;
		}

		void selectCell(Cell currentCell)
		{
			isBlockTransitioning = true;
			currentCell.setSelected(true);
			selectedCell.setBlock(currentCell.getBlock());
		}

		void moveBlock(Cell currentCell, Cell prevCell)
		{
			movesMade++;
			isBlockTransitioning = false;
			if(prevCell != null)
				prevCell.setSelected(false);
			if(currentCell == prevCell)
			{
				prevCell.setBlock(selectedCell.getBlock());
			}
			else
			{
				Block tempBlock = currentCell.getBlock();
				currentCell.setBlock(selectedCell.getBlock());
				prevCell.setBlock(tempBlock);
			}
			for(int x = 0; x < _Environment.getGrid().length; x++)
				for(int y = 0; y < _Environment.getGrid()[x].length; y++)
					_Environment.getCell(x, y).setSelected(false);

			selectedCell.removeBlock();
			environmentPane.changeTopBlocks();
			setCellsHoverable();

			/*
			isBlockTransitioning = false;
			if(prevCell != null)
				prevCell.setSelected(false);
			boolean blockAdded = false;
			for(int y = 0; y < _Environment.getGrid()[0].length; y++)
			{
				for(int x = 0; x < _Environment.getGrid().length; x++)
				{
					if(!blockAdded && x == _Environment.getLocation((Cell) currentElement).x && (y == 0 || _Environment.getGrid()[x][y - 1].hasBlock()) && !_Environment.getGrid()[x][y].hasBlock())
					{
						blockAdded = true;
						_Environment.getGrid()[x][y].setBlock(selectedCell.getBlock());
					}
				}
			}
			currentCell.setSelected(false);
			selectedCell.removeBlock();
			environmentPane.changeTopBlocks();
			GameEngine.setCellsHoverable();
		}
	}
	 */

	/**
	 * Executes the timer for the game, refreshing frames at a set rate.
	 * 
	 * @author Kevin Lewi
	 */
	public class GamePulse extends Thread
	{
		boolean ended = false;
		
		public void run()
		{

			while(Thread.currentThread() == gamePulse)
			{
				try
				{
					Thread.sleep(15);
				}
				catch(InterruptedException e)
				{
					break;
				}
				if(!interrupted)
					update();
			}
		}
		
		public void setInterrupted(boolean theBoolean){
			if(!ended){
				interrupted = theBoolean;
				gameScreen.setClickable(!theBoolean);
			}
		}
		
		public boolean isInterrupted(){
			return interrupted;
		}
		
		public void setEnded(boolean theBoolean){
			ended = theBoolean;
		}
		
		public boolean isEnded(){
			return ended;
		}
	}

}

